package org.hit.burkun.network.models;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Set;
import java.util.Map.Entry;

import org.hit.burkun.network.EdgeInfo;
import org.hit.burkun.network.SerializableGraph;
import org.hit.burkun.network.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimplePageRank {

	protected SerializableGraph graph;
	protected ArrayList<Double> pr;
	protected HashMap<Vertex, LinkedList<EdgeInfo>> matrix = new HashMap<Vertex, LinkedList<EdgeInfo>>();
	protected double zuNi = 0.2;
	protected double eps = 0.00001;
	private static Logger log = LoggerFactory.getLogger(SimplePageRank.class);
	
	
	public SimplePageRank(SerializableGraph graph) {
		this.graph = graph;
		pr = new ArrayList<Double>(graph.getAllVertexs().size());
		initPrVector(pr);
	}

	protected ArrayList<Double> doRank() {
		Collection<Vertex> vs = graph.getAllVertexs();
		ArrayList<Double> newPr = new ArrayList<Double>(graph.getAllVertexs()
				.size());
		initPrVector(newPr);
		for (Vertex v : vs) {
			// get all edges,求内积
			LinkedList<EdgeInfo> edgeInfos = matrix.get(v);
			double sums = 0;
			if (edgeInfos != null) {
				for (EdgeInfo ei : edgeInfos) {
					double tempPR = pr.get(ei.getVertex().getUid());
					sums += tempPR * ei.getScore();
				}
			} else {
				// 只有出链没有入链
			}
			// add zuni
			sums = (1-zuNi) * sums + zuNi / graph.getAllVertexs().size();
			newPr.set(v.getUid(), sums);
		}
		return newPr;
	}

	private boolean isMaxIter(ArrayList<Double> oldPr, ArrayList<Double> newPr) {
		log.info("abs error: " + absError(oldPr, newPr));
		boolean flags = true;
		for (int i = 0; i < oldPr.size(); i++) {
			if (Math.abs(oldPr.get(i) - newPr.get(i)) > eps) {
				flags = false;
				break;
			}
		}
		return flags;
	}
	
	private double absError(ArrayList<Double> oldPr, ArrayList<Double> newPr){
		double sums = 0;
		for (int i = 0; i < oldPr.size(); i++) {
			sums  += Math.abs(oldPr.get(i) - newPr.get(i));
		}
		return sums/oldPr.size();
	}
	
	

	public LinkedList<VertexWithPr> getPageRank(boolean useWeight, int maxTimes,
			double eps) {
		initMatrix(useWeight);// 不用权值
		this.eps = eps;
		int times = 0;
		while (times < maxTimes) {
			log.debug("iter times " + times);
			times += 1;
			ArrayList<Double> oldRank = doRank();
			if (isMaxIter(oldRank, pr)) {
				pr = oldRank;
				break;
			} else {
				pr = oldRank;
			}
		}
		log.debug("get the final rank values in " + times + " times");
		Collection<Vertex> allV = graph.getAllVertexs();
		LinkedList<VertexWithPr> res = new LinkedList<>();
		for(Vertex v : allV){
			res.add(new VertexWithPr(v, pr.get(v.getUid())));
		}
		//testing
		return res;
	}

	protected void initMatrix(boolean useWeight) {
		log.debug("init matrix with weight...");
		HashMap<Vertex, LinkedList<EdgeInfo>> edges = graph.getEdgeMap();
		Set<Entry<Vertex, LinkedList<EdgeInfo>>> entries = edges.entrySet();
		SerializableGraph newSg = new SerializableGraph();
		for (Entry<Vertex, LinkedList<EdgeInfo>> entry : entries) {
			Vertex v = entry.getKey();
			LinkedList<EdgeInfo> edge = entry.getValue();
			if (edge.size() != 0) {
				double sizeSum = edge.size();
				if (useWeight) {
					sizeSum = 0;
					for (EdgeInfo ei : edge) {
						//加权的值
						sizeSum += ei.getScore();
					}
				}
				for (EdgeInfo ei : edge) {
					EdgeInfo eei = null;
					if(useWeight){
						//使用自带的权值更新
						eei = new EdgeInfo(v, ei.getRelType());
						if(sizeSum > 0){
							eei.setScore((1-zuNi) * ei.getScore() / sizeSum);
						}else{
							eei.setScore(0);
						}
					}else{
						//不使用权值，当使用权值的时候，会出现所有权值消失的怪事儿
						eei = new EdgeInfo(v, ei.getRelType());
						if(sizeSum > 0){
							eei.setScore((1-zuNi) / sizeSum);						
						}else{
							eei.setScore(0);
						}
					}
					newSg.addEdge(ei.getVertex(), eei);
				}
			}
		}
		matrix = newSg.getEdgeMap();
	}

	public void initPrVector(ArrayList<Double> pr) {
		for (int i = 0; i < graph.getAllVertexs().size(); i++) {
			pr.add(1.0 / graph.getAllVertexs().size());
		}
	}
	
	

	public void setZuni(double zuni) {
		this.zuNi = zuni;
	}

	public static  class VertexWithPr{
		private Vertex vertex;
		private double pr;
		public VertexWithPr(Vertex vertex, double pr) {
			this.vertex = vertex;
			this.pr = pr;
		}
		public Vertex getVertex() {
			return vertex;
		}
		public void setVertex(Vertex vertex) {
			this.vertex = vertex;
		}
		public double getPr() {
			return pr;
		}
		public void setPr(double pr) {
			this.pr = pr;
		}
	}
	
	public static class PangeRankOutPut implements Comparable<PangeRankOutPut>{
		private BigDecimal pr;
		private String cui;
		private String dbid;
		@Override
		public int compareTo(PangeRankOutPut o) {
			return pr.compareTo(o.getPr());
		}
		public BigDecimal getPr() {
			return pr;
		}
		public void setPr(BigDecimal pr) {
			this.pr = pr;
		}
		public String getCui() {
			return cui;
		}
		public void setCui(String cui) {
			this.cui = cui;
		}
		
		public String getDbid() {
			return dbid;
		}
		public void setDbid(String dbid) {
			this.dbid = dbid;
		}
		@Override
		public String toString() {
			return cui + " " + dbid +" "+ pr.doubleValue();
		}
	}
	

}
